/**
 * Copyright (C) 2011  Robin Dietrich, Nils Kenneweg, Leonard Stutz
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 * 
 **/

#include "chunkManager.hpp"

namespace minecraft {
    chunkManager::chunkManager() {
        this->coquit = false;
    }

    chunkManager::~chunkManager() {
        this->coquit = true;
        //iterators are expensive and as we should have the complete
        //list of elements in the vector, we can just iterate it by size
        for (unsigned int i = 0; i < this->chunks_created.size(); ++i) {
            //free the object
            delete this->chunks[this->chunks_created[i]];
        }
    }
    
    void chunkManager::preloadWorker(int x, int y, int z) {
        std::string id = coordinatesToChunkId(x, y, z);
        chunk *c = new chunk(x, y, z);

        //from here on locking is required, writing to maps is never
        //guaranted to be thread safe
        boost::lock_guard<boost::mutex> lock(p_lock);
        this->chunks[id] = c;
        this->chunks_created.push_back(id);

        //delete element if loaded chunks > chunksmax
        if (this->chunks.size() > P_CHUNKSMAX) {
            delete this->chunks[this->chunks_created[0]];
            this->chunks.erase(this->chunks_created[0]);
            this->chunks_created.erase(this->chunks_created.begin());
        }
    }
    
    chunk* chunkManager::getChunk(int x, int y, int z) {
        if (this->coquit) return NULL;
        std::string id = coordinatesToChunkId(x, y, z);       
        
		bool found = true;		
         
		p_lock.lock();
        if (this->chunks.find(id) == this->chunks.end()) {
			//std::cout << "loading chunk" << coordinatesToChunkId(x, y, z) << "\n";
		
			//std::cout << "Loading Chunk in Thread: " << boost::this_thread::get_id() << "\n" << std::flush;
			found = false;
        }
        
		if (found) {
			chunk* c = this->chunks[id];
			p_lock.unlock();
			//return chunk
			return c;
		} else {
			p_lock.unlock();
			
			chunk *c = new chunk(x, y, z);			
             
			p_lock.lock();
				this->chunks[id] = c;
				this->chunks_created.push_back(id);
				
				//delete element if loaded chunks > chunksmax
				if (this->chunks.size() > P_CHUNKSMAX) {
					delete this->chunks[this->chunks_created[0]];
					this->chunks.erase(this->chunks_created[0]);
					this->chunks_created.erase(this->chunks_created.begin());
				}
			p_lock.unlock();
			
			return c;
		}
    }
    
    b_type chunkManager::getBlock(int x, int y, int z) {
        //first, get chunk x / y / z
		int c_x, c_y, c_z, b_x, b_y, b_z;
		
		if (x >= 0 ) {
			c_x = (int) (x / P_CHUNKSIZE);
		} else {
			c_x = (int)  ((x + 1 - P_CHUNKSIZE) / P_CHUNKSIZE);
		}

		if (y >= 0 ) {
			c_y = (int) (y / P_CHUNKSIZE);
		} else {
			c_y = (int)  ((y + 1 - P_CHUNKSIZE) / P_CHUNKSIZE);
		}
		
		if (z >= 0 ) {
			c_z = (int) (z / P_CHUNKSIZE);
		} else {
			c_z = (int)  ((z + 1 - P_CHUNKSIZE) / P_CHUNKSIZE);
		}
		
		b_x = x-(P_CHUNKSIZE*c_x);
		b_y = y-(P_CHUNKSIZE*c_y);
		b_z = z-(P_CHUNKSIZE*c_z);
        
        chunk* c = this->getChunk(c_x, c_y, c_z);
		
		/**
		std::cout << "K:" << x << ":" << y << ":" << z;
		std::cout << " C:" << c_x << ":" << c_y << ":" << c_z;
		std::cout << " B:" << b_x << ":" << b_y << ":" << b_z;
		
		std::cout << "\n";**/
		
        return c->get(b_x, b_y, b_z);   
    }
    
    void chunkManager::setBlock(int x, int y, int z, b_type val) {
        //same as getBlock function
        int c_x = ((int) x / P_CHUNKSIZE);
        int c_y = ((int) y / P_CHUNKSIZE);
        int c_z = ((int) z / P_CHUNKSIZE);        
        
        //check if c_x == 0 but x < 0 && x > -64
        if (x < 0 && c_x == 0) c_x = -1;
        if (y < 0 && c_y == 0) c_y = -1;
        if (z < 0 && c_z == 0) c_z = -1;
        
        chunk* c = this->getChunk(c_x, c_y, c_z);
        
        //now set the corresponding block
        c->update(x-(P_CHUNKSIZE*c_x), y-(P_CHUNKSIZE*c_y), z-(P_CHUNKSIZE*c_z), val);
    }
    
	Ogre::ManualObject* chunkManager::renderChunk(int cx, int cy, int cz) {
        //get the chunk and create mesh for it
        chunk* c = this->getChunk(cx, cy, cz);
        Ogre::ManualObject* rChunk = new Ogre::ManualObject("chunk_" + c->getId());
        
        if (c->isEmpty()) {
            //return if empty
            return rChunk;
        }
        
        //iterate chunk types
        b_tlist types = c->getTypes();
        for (b_tlist::const_iterator it = types.begin(); it != types.end(); ++it) {
            //current block we are looking at
            boost::shared_ptr<block> type = sBlockManager::Instance()->create(*it);
            rChunk->begin(type->getTextureName());
            
            //helper variable for vertex optimization
            unsigned int vert = 0;
            
            for (unsigned int x = 0; x < P_CHUNKSIZE; ++x) {
                for (unsigned int y = 0; y < P_CHUNKSIZE; ++y) {
                    for (unsigned int z = 0; z < P_CHUNKSIZE; ++z) {
                        //check if the block requested has the current material
                        b_type block = c->get(x,y,z);
                        if (block == (*it)) {							
							if(block == 0) continue;
							
                            //quicksave global coordinate
                            int gx = (cx*P_CHUNKSIZE)+x;
                            int gy = (cy*P_CHUNKSIZE)+y;
                            int gz = (cz*P_CHUNKSIZE)+z;
                            
                            //x position -1 / +1
							if(x > 0){
							  block = c->get(x-1,y,z);
							} else{
							  block = this->getBlock(gx-1,gy,gz);
							}
                            
							if(block == 0){
                                rChunk->position(x, y, z+1);   rChunk->normal(-1, 0, 0); rChunk->textureCoord(0, 1);
                                rChunk->position(x, y+1, z+1); rChunk->normal(-1, 0, 0); rChunk->textureCoord(1, 1);
                                rChunk->position(x, y+1, z);   rChunk->normal(-1, 0, 0); rChunk->textureCoord(1, 0);
                                rChunk->position(x, y, z);     rChunk->normal(-1, 0, 0); rChunk->textureCoord(0, 0);
                                
								rChunk->triangle(vert, vert+1, vert+2); rChunk->triangle(vert+2, vert+3, vert);                                
                                vert += 4;
                            }
                            
							if(x+1 < P_CHUNKSIZE){
                                block = c->get(x+1,y,z);
							} else{
                                block = this->getBlock(gx+1,gy,gz);
							}
							if(block == 0){
                                rChunk->position(x+1, y, z);     rChunk->normal(1, 0, 0); rChunk->textureCoord(0, 1);
                                rChunk->position(x+1, y+1, z);   rChunk->normal(1, 0, 0); rChunk->textureCoord(1, 1);
                                rChunk->position(x+1, y+1, z+1); rChunk->normal(1, 0, 0); rChunk->textureCoord(1, 0);
                                rChunk->position(x+1, y, z+1);   rChunk->normal(1, 0, 0); rChunk->textureCoord(0, 0);
                                
								rChunk->triangle(vert, vert+1, vert+2); rChunk->triangle(vert+2, vert+3, vert);                                
                                vert += 4;
                            }
                            
                            //y position -1 / +1
							if(y > 0){
							  block = c->get(x,y-1,z);
							} else{
							  block = this->getBlock(gx,gy-1,gz);
							}
							if(block == 0){
                                rChunk->position(x, y, z);     rChunk->normal(0, -1, 0); rChunk->textureCoord(0, 1);
                                rChunk->position(x+1, y, z);   rChunk->normal(0, -1, 0); rChunk->textureCoord(1, 1);
                                rChunk->position(x+1, y, z+1); rChunk->normal(0, -1, 0); rChunk->textureCoord(1, 0);
                                rChunk->position(x, y, z+1);   rChunk->normal(0, -1, 0); rChunk->textureCoord(0, 0);
                                
								rChunk->triangle(vert, vert+1, vert+2); rChunk->triangle(vert+2, vert+3, vert);                                
                                vert += 4;
                            }
                            
							if(y+1 < P_CHUNKSIZE){
							  block = c->get(x,y+1,z);
							} else{
							  block = this->getBlock(gx,gy+1,gz);
							}
							if(block == 0){
                                rChunk->position(x, y+1, z+1);   rChunk->normal(0, 1, 0); rChunk->textureCoord(0, 1);
                                rChunk->position(x+1, y+1, z+1); rChunk->normal(0, 1, 0); rChunk->textureCoord(1, 1);
                                rChunk->position(x+1, y+1, z);   rChunk->normal(0, 1, 0); rChunk->textureCoord(1, 0);
                                rChunk->position(x, y+1, z);     rChunk->normal(0, 1, 0); rChunk->textureCoord(0, 0);
                                
								rChunk->triangle(vert, vert+1, vert+2); rChunk->triangle(vert+2, vert+3, vert);                                
                                vert += 4;
                            }
                            
                            //z position -1 / +1
							if(z > 0){
							  block = c->get(x,y,z-1);
							} else{
							  block = this->getBlock(gx,gy,gz-1);
							}
							if(block == 0){
                                rChunk->position(x, y+1, z);   rChunk->normal(0, 0, -1); rChunk->textureCoord(0, 1);
                                rChunk->position(x+1, y+1, z); rChunk->normal(0, 0, -1); rChunk->textureCoord(1, 1);
                                rChunk->position(x+1, y, z);   rChunk->normal(0, 0, -1); rChunk->textureCoord(1, 0);
                                rChunk->position(x, y, z);     rChunk->normal(0, 0, -1); rChunk->textureCoord(0, 0);
                                
								rChunk->triangle(vert, vert+1, vert+2); rChunk->triangle(vert+2, vert+3, vert);                                
                                vert += 4;
                            }
                            
							if(z+1 < P_CHUNKSIZE){
							  block = c->get(x,y,z+1);
							} else{
							  block = this->getBlock(gx,gy,gz+1);
							}
							if(block == 0){
                                rChunk->position(x, y, z+1);     rChunk->normal(0, 0, 1); rChunk->textureCoord(0, 1);
                                rChunk->position(x+1, y, z+1);   rChunk->normal(0, 0, 1); rChunk->textureCoord(1, 1);
                                rChunk->position(x+1, y+1, z+1); rChunk->normal(0, 0, 1); rChunk->textureCoord(1, 0);
                                rChunk->position(x, y+1, z+1);   rChunk->normal(0, 0, 1); rChunk->textureCoord(0, 0);
                                
								rChunk->triangle(vert, vert+1, vert+2); rChunk->triangle(vert+2, vert+3, vert);                                
                                vert += 4;
                            }
                        }
						
						//end of for loop
						
						//exit point.
                    }
                }
            }
            
            //end current material
            rChunk->end();
        }
        
        return rChunk;        
    }
}

